﻿using System.Linq.Dynamic.Core;
using System.Reflection;
using Devonline.Core;
using Devonline.Entity;
using Microsoft.AspNetCore.OData;
using Microsoft.AspNetCore.OData.NewtonsoftJson;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Query.Wrapper;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OData.Edm;
using Microsoft.OData.ModelBuilder;

namespace Devonline.AspNetCore.OData;

public static class ODataExtensions
{
    private static readonly Type _type = typeof(ODataExtensions);
    /// <summary>
    /// 按照默认的命名约定, 从数据库上下文中注册的实体类型名字中注册 odata 实体类型
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文实例类型</typeparam>
    /// <typeparam name="TKey">实体数据主键类型</typeparam>
    /// <param name="builder">ODataModelBuilder 实例</param>
    /// <returns></returns>
    public static ODataModelBuilder GetEdmModels<TDbContext, TKey>(this ODataModelBuilder builder) where TDbContext : DbContext
    {
        var entitySets = typeof(TDbContext).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.PropertyType.IsGenericType);
        foreach (var entitySet in entitySets)
        {
            var entitySetTypes = entitySet.PropertyType.GetGenericArguments();
            if (entitySetTypes.Any(x => x.IsFromType<IEntitySet<TKey>>()))
            {
                //builder.EntitySet<Item>(nameof(ApplicationDbContext.Items));
                //builder.EntityType<Item>().CollectionProperty(x => x.Additionals).IsNavigable();
                builder.InvokeGenericMethod(nameof(builder.EntitySet), new object[] { entitySet.Name }, out object? returnValue, entitySetTypes);
            }
        }

        return builder;
    }
    /// <summary>
    /// 按照默认的命名约定, 从数据库上下文中注册的实体类型名字中注册 odata 实体类型的字符串主键默认实现
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文实例类型</typeparam>
    /// <param name="builder">ODataModelBuilder 实例</param>
    /// <returns></returns>
    public static ODataModelBuilder GetEdmModels<TDbContext>(this ODataModelBuilder builder) where TDbContext : DbContext => builder.GetEdmModels<TDbContext, string>();
    /// <summary>
    /// 构建 EdmModel 模型, 如果是使用 ODataConventionModelBuilder 则根据 camelCase 设置是否使用驼峰法字段名
    /// </summary>
    /// <param name="builder">ODataModelBuilder 实例</param>
    /// <param name="camelCase">是否使用驼峰法命名</param>
    /// <returns></returns>
    public static IEdmModel BuildEdmModel(this ODataModelBuilder builder, bool camelCase = true)
    {
        if (camelCase && builder is ODataConventionModelBuilder odataBuilder)
        {
            builder = odataBuilder.EnableLowerCamelCase();
        }

        return builder.GetEdmModel();
    }

    /// <summary>
    /// 添加默认 OData 设置
    /// </summary>
    /// <typeparam name="TDbContext"></typeparam>
    /// <param name="builder"></param>
    /// <returns></returns>
    public static IMvcBuilder AddDefaultOData<TDbContext>(this IMvcBuilder builder) where TDbContext : DbContext
    {
        return builder.AddOData(options => options.Count().Filter().Expand().Select().OrderBy().SetMaxTop(AppSettings.UNIT_HUNDRED)
         .AddRouteComponents(AppSettings.DEFAULT_ODATA_ROUTE_PREFIX, new ODataConventionModelBuilder().GetEdmModels<TDbContext>().BuildEdmModel(true)))
         .AddODataNewtonsoftJson();
    }

    /// <summary>
    /// 从当前 odata 上下文获取执行结果
    /// </summary>
    /// <typeparam name="T">业务数据对象模型</typeparam>
    /// <param name="options">odata 查询表达式选项</param>
    /// <returns></returns>
    public static async Task<IEnumerable<T>> ApplyToAsync<T>(this ODataQueryOptions<T> options, IQueryable<T> queryable) where T : class, new()
    {
        var list = await options.ApplyTo(queryable).ToDynamicListAsync();
        if (list is null || list.Count <= 0)
        {
            return new List<T>(0);
        }

        return list.ConvertAll<T>(x => WrapResult<T>(x));
    }
    /// <summary>
    /// 从当前 odata 上下文获取执行结果
    /// </summary>
    /// <typeparam name="TSource">原业务数据对象模型</typeparam>
    /// <typeparam name="TResult">执行后的模型</typeparam>
    /// <param name="options">odata 查询表达式选项</param>
    /// <returns></returns>
    public static async Task<IEnumerable<TResult>> ApplyToAsync<TSource, TResult>(this ODataQueryOptions<TSource> options, IQueryable<TSource> queryable) where TResult : class, new()
    {
        var list = await options.ApplyTo(queryable).ToDynamicListAsync();
        if (list is null || list.Count <= 0)
        {
            return new List<TResult>(0);
        }

        return list.ConvertAll<TResult>(x =>
        {
            if (x is TSource sourceResult)
            {
                return sourceResult.CopyTo<TResult>();
            }

            return WrapResult<TResult>(x);
        });
    }
    /// <summary>
    /// 将 ODataQueryOptions 查询表达式执行后的返回结果 dynamic 类型转换为 TResult 类型
    /// </summary>
    /// <typeparam name="TResult"></typeparam>
    /// <param name="source"></param>
    /// <returns></returns>
    public static TResult WrapResult<TResult>(dynamic source) where TResult : class, new()
    {
        if (source is TResult result)
        {
            return result;
        }

        result = new TResult();
        if (source is ISelectExpandWrapper wrapper)
        {
            var propertyInfos = typeof(TResult).GetProperties(BindingFlags.Public | BindingFlags.Instance);
            var dic = wrapper.ToDictionary();
            foreach (var keyValuePair in dic)
            {
                var propertyInfo = propertyInfos.FirstOrDefault(x => x.Name == keyValuePair.Key);
                if (propertyInfo is not null)
                {
                    if (keyValuePair.Value is ISelectExpandWrapper)
                    {
                        _type.InvokeGenericMethod(nameof(WrapResult), new object[] { keyValuePair.Value }, out object? value, propertyInfo.PropertyType);
                        if (value is not null)
                        {
                            propertyInfo.SetValue(result, value);
                        }
                    }
                    else
                    {
                        propertyInfo.SetValue(result, keyValuePair.Value);
                    }
                }
            }
        }

        return result;
    }
}